package util;


import cn.novelweb.video.edit.VideoEditing;
import cn.novelweb.video.format.FormatConversion;
import cn.novelweb.video.pojo.VideoParameters;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_videoio;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.util.CollectionUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static org.bytedeco.javacpp.opencv_imgcodecs.cvSaveImage;
import static org.bytedeco.javacpp.opencv_videoio.*;
import static org.opencv.videoio.Videoio.CV_CAP_PROP_FRAME_COUNT;
import static org.opencv.videoio.Videoio.CV_CAP_PROP_POS_FRAMES;

/**
 * util
 *
 * @description: 视频工具
 * @author: yechen
 * @email: wangqingfeng@wxyundian.com
 * @date: 2020年09月17日 19:09
 */
@Slf4j
public class VideoUtil {
    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFile 源视频文件
     * @param saveFile  截取帧的图片存放路径
     * @throws Exception
     */
    public static List<File> fetchPic(File videoFile, String saveFile, int second) throws Exception {

        java.util.List<File> files = new ArrayList<>();

        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
        ff.start();
        int lenght = ff.getLengthInAudioFrames();
        System.out.println(ff.getFrameRate());

        int i = 0;
        Frame frame = null;

        while (i < lenght) {
            // 过滤前5帧，避免出现全黑的图片，依自己情况而定
            frame = ff.grabImage();
            if (i >= (int) (ff.getFrameRate() * second) && frame.image != null) {
                System.out.print(i + ",");
                if (frame != null && frame.image != null) {
                    System.out.println(i);
                    files.add(writeToFile(frame, saveFile, i));
                }
                second++;
            }
            i += second;
        }
        ff.stop();
        return files;
    }

    public static List<Integer> getList(int count, int length) {
        if (count > length) {
            count = length;
        }
        System.out.println(length);
        System.out.println(count);
        int total = (int) (length / count);
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            list.add(i * total);
            System.out.println(i * total);
        }
        return list;
    }

    public static List<File> fetchPicByCount(File videoFile, String saveFile, int count) throws Exception {

        java.util.List<File> files = new ArrayList<>();

        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
        ff.start();

        int frameLength = ff.getLengthInFrames();


        System.out.println("length:" + frameLength);

        List<Integer> list = getList(count, frameLength);

        System.out.println(ff.getFrameRate());

        int i = 0;
        Frame frame = null;

        while (i < frameLength) {
            frame = ff.grabImage();
            if (list.contains(i)) {
                if (frame != null && frame.image != null) {
                    System.out.println(i);
                    files.add(writeToFile(frame, saveFile, i));
                }
            }
            i++;
        }
        ff.stop();
        return files;
    }


    public static File writeToFile(Frame frame, String saveFile, int second) throws Exception {
        String fileName = String.valueOf(System.currentTimeMillis()) + second;
        File targetFile = new File(saveFile + File.separator + fileName + ".jpg");
        String imgSuffix = "jpg";

        Java2DFrameConverter converter = new Java2DFrameConverter();
        BufferedImage srcBi = converter.getBufferedImage(frame);
        int owidth = srcBi.getWidth();
        int oheight = srcBi.getHeight();
        // 对截取的帧进行等比例缩放
        int width = 800;
        int height = (int) (((double) width / owidth) * oheight);
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
        bi.getGraphics().drawImage(srcBi.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
        try {
            ImageIO.write(bi, imgSuffix, targetFile);
        } catch (Exception e) {
            throw new Exception("截帧失败");
        }
        return targetFile;
    }


    /**
     * 获取视频时长，单位为秒
     *
     * @param file
     * @return 时长（s）
     */
    public static Long getVideoTime(File file) {
        Long times = 0L;
        try {
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file);
            ff.start();
            times = ff.getLengthInTime() / (1000 * 1000);
            ff.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return times;
    }

    /**
     * 截取视频中的某一段，生成新视频
     *
     * @param videoFile  源视频路径
     * @param outputFile 转换后的视频路径
     * @param startTime  开始抽取的时间点，单位:s
     * @param timeLength 需要抽取的时间段，单位:s；例如：该参数值为10时即抽取从startTime开始之后10秒的视频作为新视频
     */
    public static void cutVideo(File videoFile, File outputFile, Time startTime, int timeLength, String ffmpegExePath) {
        if (videoFile == null || !videoFile.exists()) {
            throw new RuntimeException("视频文件不存在：" + videoFile.getAbsolutePath());
        }
        if (null == outputFile) {
            throw new RuntimeException("转换后的视频路径为空，请检查转换后的视频存放路径是否正确");
        }
        Long duration = getVideoTime(videoFile);
        if (duration != null && startTime.getTime() + timeLength > duration) {
            throw new RuntimeException("截取时间不合法：" + startTime.toString() + "，因为截取时间大于视频的时长");
        }
        try {
            if (!outputFile.exists()) {
                outputFile.createNewFile();
            }
            List<String> commond = new ArrayList<String>();
            commond.add("-ss");
            commond.add(startTime.toString());
            commond.add("-t");
            commond.add("" + timeLength);
            commond.add("-i");
            commond.add(videoFile.getAbsolutePath());
            commond.add("-vcodec");
            commond.add("copy");
            commond.add("-acodec");
            commond.add("copy");
            commond.add(outputFile.getAbsolutePath());
            executeCommand(commond, ffmpegExePath);
        } catch (IOException e) {
            log.error("--- 视频截取过程出错 ---");
        }
    }

    /**
     * 执行FFmpeg命令
     *
     * @param commonds FFmpeg命令
     * @return FFmpeg执行命令过程中产生的各信息，执行出错时返回null
     */
    public static String executeCommand(List<String> commonds, String ffmpegPath) {
        if (CollectionUtils.isEmpty(commonds)) {
            log.error("--- 指令执行失败，因为要执行的FFmpeg指令为空！ ---");
        }
        LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
        ffmpegCmds.addFirst(ffmpegPath); // 设置ffmpeg程序所在路径

        Runtime runtime = Runtime.getRuntime();
        Process ffmpeg = null;
        try {
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(ffmpegCmds);
            ffmpeg = builder.start();
            String result = waitForExcute(ffmpeg);

            // 输出执行的命令信息
            String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
            String resultStr = result != null ? "正常" : "【异常】";
            log.info("--- FFmepg命令： ---" + cmdStr + " 已执行完毕,执行结果：" + resultStr);
            return result;

        } catch (IOException e) {
            log.error("--- FFmpeg命令执行出错！ --- 出错信息： " + e.getMessage());
            return null;

        } finally {
            if (null != ffmpeg) {
                ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
                // JVM退出时，先通过钩子关闭FFmepg进程
                runtime.addShutdownHook(ffmpegKiller);
            }
        }
    }

    /**
     * FFmpeg进程执行输出，必须使用此函数，否则会出现进程阻塞现象
     * 当FFmpeg进程执行完所有命令后，本函数返回FFmpeg进程退出时的状态值；
     * @param process
     * @return 进程执行命令过程中产生的各种信息，执行命令过程出错时返回null
     */
    private static String waitForExcute(Process process) {
        InputStream inputStream = null;
        InputStream errorStream = null;
        BufferedReader br1 = null;
        BufferedReader br2 = null;
        StringBuffer returnStr = new StringBuffer(); // 存储FFmpeg执行命令过程中产生的信息
        int exitValue = -1;
        try {
            inputStream = process.getInputStream();
            errorStream = process.getErrorStream();
            br1 = new BufferedReader(new InputStreamReader(inputStream));
            br2 = new BufferedReader(new InputStreamReader(errorStream));
            boolean finished = false;
            while (!finished) {
                try { // while内部使用一个try-catch块，这样当某一次循环读取抛出异常时，可以结束当次读取，返回条件处开始下一次读取
                    String line1 = null;
                    String line2 = null;
                    while ((line1 = br1.readLine()) != null) {
                        log.info(line1);
                    }
                    while ((line2 = br2.readLine()) !=  null) {
                        log.info(line2);
                        returnStr.append(line2 + "\n");
                    }
                    exitValue = process.exitValue();
                    finished = true;
                } catch (IllegalThreadStateException e) { // 防止线程的start方法被重复调用
                    log.error("--- 本次读取标准输出流或错误流信息出错 --- 错误信息： " + e.getMessage());
                    Thread.sleep(500);
                } catch (Exception e2) {
                    log.error("--- 本次读取标准输出流或错误流信息出错 --- 错误信息： " + e2.getMessage());
                }
            }
            return returnStr.toString();
        } catch (Exception e) {
            log.error("--- 执行FFmpeg程序时读取标准输出或错误流的信息出错 ---");
            return null;
        } finally {
            try {
                if (null != br1) {
                    br1.close();
                }
                if (null != br2) {
                    br2.close();
                }
                if (null != inputStream) {
                    inputStream.close();
                }
                if (null != errorStream) {
                    errorStream.close();
                }
            } catch (IOException e) {
                log.error("--- 关闭读取的标准输出流或错误流时出错 ---");
            }

        }

    }

    public static void getBySecond(String filePath, String directory) {
        opencv_videoio.CvCapture capture = cvCaptureFromFile(filePath);
        //帧率
        double fps = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_COUNT);
        System.out.println("帧率:" + fps);
        opencv_core.IplImage frame = null;
        double pos1 = 0;

        double rootCount = 0;
        while (true) {

            //读取关键帧
            frame = cvQueryFrame(capture);

            rootCount = fps;
            while (rootCount > 0) {
                //这一段的目的是跳过每一秒钟的帧数,也就是说fps是帧率(一秒钟有多少帧),在读取一帧后,跳过fps数量的帧就相当于跳过了1秒钟。
                frame = cvQueryFrame(capture);
                rootCount--;
            }

            //获取当前帧的位置
            pos1 = cvGetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES);
            System.out.println(pos1);

            if (null == frame)
                break;

            cvSaveImage("E:/223/" + pos1 + ".jpg", frame);

        }

        cvReleaseCapture(capture);
    }

    /*public void getBySecond() {
        opencv_videoio.CvCapture capture = opencv_highgui.cvC("D:/085402.crf");



        //帧率
        int fps = (int) opencv_highgui.cvGetCaptureProperty(capture, opencv_highgui.CV_CAP_PROP_FPS);
        System.out.println("帧率:"+fps);

        opencv_core.IplImage frame = null;
        double pos1 = 0;

        int rootCount = 0;

        while (true) {

            //读取关键帧
            frame = opencv_highgui.cvQueryFrame(capture);

            rootCount = fps;
            while(rootCount > 0 ){
                //这一段的目的是跳过每一秒钟的帧数,也就是说fps是帧率(一秒钟有多少帧),在读取一帧后,跳过fps数量的帧就相当于跳过了1秒钟。
                frame = opencv_highgui.cvQueryFrame(capture);
                rootCount--;
            }

            //获取当前帧的位置
            pos1 = opencv_highgui.cvGetCaptureProperty(capture,opencv_highgui.CV_CAP_PROP_POS_FRAMES);
            System.out.println(pos1);

            if (null == frame)
                break;

            opencv_highgui.cvSaveImage("d:/img/" + pos1 + ".jpg",frame);

        }

        opencv_highgui.cvReleaseCapture(capture);
    }*/

//    public static void main(String[] args) {
//        try {
//            getList(10,113);
//
//            long start = System.currentTimeMillis();
//            File file = new File("E:/2.mp4");
//            System.out.println(VideoUtil.getVideoTime(file));
//            System.out.println("cost " + (System.currentTimeMillis() - start) + "ms");
//            VideoUtil.getBySecond("E:/2.mp4",null);
//            List<File> files = VideoUtil.fetchPicByCount(file, "E:/22", 10);
//            System.out.println(files.get(0).getName());
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }

    /**
     * 在程序退出前结束已有的FFmpeg进程
     */
    private static class ProcessKiller extends Thread {
        private Process process;

        public ProcessKiller(Process process) {
            this.process = process;
        }

        @Override
        public void run() {
            this.process.destroy();
            log.info("--- 已销毁FFmpeg进程 --- 进程名： " + process.toString());
        }
    }

    public static void main(String[] args) {
        VideoParameters videoParameters = FormatConversion.getVideoParameters("E:/2.mp4");
        String input = "/2.mp4";
        String output = "/2_1.mp4";

        VideoEditing.clipVideo("0", "10", input, output, System.out::println);
    }
}
